package com.snow.starter.global;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.snow.annotation.SysLog;
import com.snow.exception.CommonRuntimeException;
import com.snow.model.ILoginUser;
import com.snow.common.entity.SystemLogEntity;
import com.snow.common.repository.SystemLogRepository;
import com.snow.service.IAuthComponent;
import com.snow.starter.SnowAutoConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

/**
 * @author 13119
 */
@Slf4j
public class AccessFilter extends OncePerRequestFilter {

    private final SystemLogRepository systemLogRepository;
    private final IAuthComponent authComponent;
    private final SnowAutoConfiguration snowAutoConfiguration;

    public AccessFilter(SystemLogRepository systemLogRepository, IAuthComponent authComponent, SnowAutoConfiguration snowAutoConfiguration) {
        this.systemLogRepository = systemLogRepository;
        this.authComponent = authComponent;
        this.snowAutoConfiguration = snowAutoConfiguration;
        log.info("AccessFilter过滤器已启动");
    }

//    @Autowired(required = false)
//    NacosDiscoveryProperties nacosDiscoveryProperties;

    final String traceFlag = "x-trace";
    final String backupTraceFlag = "x-nonce";

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
                                    @NonNull FilterChain filterChain) throws ServletException, IOException {
        // 包装请求流
        // 在 doFilterInternal 方法中包装 request
        if (!(request instanceof ContentCachingRequestWrapper)) {
            log.debug("使用ContentCachingRequestWrapper包装请求流");
            request = new ContentCachingRequestWrapper(request);
        }
        if (request.getServletPath().contains("swagger") || request.getServletPath().startsWith("/static-resources")) {
            filterChain.doFilter(request, response);
            return;
        }
        String traceId = request.getHeader(traceFlag);
        if (StrUtil.isBlank(traceId)) {
            traceId = request.getHeader(backupTraceFlag);
            if (StrUtil.isBlank(traceId)) {
                traceId = request.getHeader("authorization");
                if (StrUtil.isBlank(traceId)) {
                    traceId = IdUtil.fastSimpleUUID();
                    response.setHeader(traceFlag, traceId);
                }
            }
        }
        MDC.put(traceFlag, traceId);
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization,x-nonce,x-timestamp,x-trace,x-sign,X_Requested_With,X-Frame-Options,version,client-version");
        TimeInterval timer = DateUtil.timer();
        log.info("请求客户端版本：{}", request.getHeader("client-version"));
        log.info("{} 请求开始执行：", request.getRequestURI());
        log.info("请求query参数：{}", request.getQueryString());
        String contentType = request.getContentType();
        if (contentType != null && (contentType.startsWith("multipart/form-data")
                || contentType.startsWith("application/octet-stream"))) {
            log.info("请求body参数：[文件上传请求，跳过body记录]");
        } else {
            log.info("请求body参数：{}", getRequestBody(request));
        }
        if (snowAutoConfiguration.getEnableSignValidate() && !StrUtil.endWithAnyIgnoreCase(request.getRequestURI(), ".png", ".jpg", ".jpeg", ".pdf", ".txt", ".icon")) {
            validateSign(request);
        }
        filterChain.doFilter(request, response);
        saveLog(request);
        log.info("{} 请求执行结束，共耗时：{}({}ms)", request.getRequestURI(), timer.intervalPretty(), timer.interval());
        log.info("请求结果：{}", response.getStatus());
    }

    // 添加辅助方法
    private String getRequestBody(HttpServletRequest request) {
        if (request instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                try {
                    return new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                } catch (Exception e) {
                    return "[无法解析请求体]";
                }
            }
        }
        return "";
    }

    private void saveLog(HttpServletRequest request) {
        SystemLogEntity systemLog = new SystemLogEntity();
        systemLog.setMessage("");
        Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
        if (handler instanceof HandlerMethod) {
            SysLog syslog = ((HandlerMethod) handler).getMethod().getAnnotation(SysLog.class);
            if (syslog == null) {
                log.warn("接口未记录日志，建议添加：{}", request.getRequestURI());
                return;
            }
            systemLog.setMessage(syslog.value());
        }
        systemLog.setType(0);
        systemLog.setIp(NetUtil.getMultistageReverseProxyIp(request.getHeader("X-Forwarded-For")));
        systemLog.setPath(request.getRequestURI());
        systemLog.setTraceId(MDC.get(traceFlag));
        try {
            ILoginUser currentUser = authComponent.getCurrentUser();
            systemLog.setCreateInfo(currentUser);
            systemLog.setTenantId(currentUser.getTenantId());
        } catch (Exception e) {
            systemLog.setCreateName("登录用户信息获取失败：" + e.getMessage());
            systemLog.setCreateTime(new Date());
        }
        systemLogRepository.save(systemLog);
    }

    private void validateSign(@NonNull HttpServletRequest request) {
        String xSign = request.getHeader("x-sign");
        String xTimestamp = request.getHeader("x-timestamp");
        String xNonce = request.getHeader("x-nonce");
        if (StrUtil.isBlank(xSign) || StrUtil.isBlank(xTimestamp) || StrUtil.isBlank(xNonce)) {
            throw new CommonRuntimeException("非法请求，请核验签名");
        }
        String validSign = SecureUtil.md5("nonce=[" + xNonce + "]&timestamp=[" + xTimestamp + "]");
        if (Math.abs(Long.parseLong(xTimestamp) - new Date().getTime()) > 1000 * 60 * 3) {
            log.error("非法请求，时间超时，请校准客户端时间：{}", xTimestamp);
            throw new CommonRuntimeException("非法请求");
        }
        if (!StrUtil.equalsIgnoreCase(validSign, xSign)) {
            log.error("非法请求： sign校验未通过,请求Sign:{},计算sign:{}", xSign, validSign);
            throw new CommonRuntimeException("非法请求");
        }
    }
}
